{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Efficient use of `pytest` fixtures"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Required boilerplate for using `pytest` inside notebooks."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Let's make sure pytest and ipytest packages are installed\n",
    "# ipytest is required for running pytest inside Jupyter notebooks\n",
    "import sys\n",
    "!{sys.executable} -m pip install pytest 'ipytest>=0.3.0'\n",
    "\n",
    "import pytest\n",
    "from ipytest import magics, clean_tests\n",
    "__file__ = 'pytest_fixtures.ipynb'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Parametrizing fixtures\n",
    "Similarly as you can parametrize test functions with `pytest.mark.parametrize`, you can parametrize fixtures:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "PATHS = ['/foo/bar.txt', '/bar/baz.txt']\n",
    "\n",
    "@pytest.fixture(params=PATHS)\n",
    "def executable(request):\n",
    "    return request.param"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%run_pytest[clean] '-s'\n",
    "\n",
    "def test_something_with_executable(executable):\n",
    "    print(executable)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## [`pytest.mark.usefixtures`](https://docs.pytest.org/en/latest/fixture.html#usefixtures)\n",
    "[`pytest.mark.usefixtures`](https://docs.pytest.org/en/latest/fixture.html#usefixtures) is useful especially when you want to use some fixture in a set of tests but you don't need the return value of the fixture."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "@pytest.fixture\n",
    "def my_fixture():\n",
    "    print('\\nmy_fixture is used')\n",
    "\n",
    "@pytest.fixture\n",
    "def other_fixture():\n",
    "    return 'FOO'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%run_pytest[clean] '-s'\n",
    "\n",
    "@pytest.mark.usefixtures('my_fixture')\n",
    "class TestMyStuff:\n",
    "    def test_somestuff(self):\n",
    "        pass\n",
    "    \n",
    "    def test_some_other_stuff(self, other_fixture):\n",
    "        print('here we use also other_fixture which returns: {}'.format(other_fixture))\n",
    "        pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Needed to clean up test classes (Test*) in ipytest\n",
    "clean_tests('Test*')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## `pytest` [built-in fixtures](https://docs.pytest.org/en/latest/builtin.html#pytest-api-and-builtin-fixtures)\n",
    "Here are a couple of examples of the useful built-in fixtures, you can view all available fixtures by running `pytest --fixtures`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### [`monkeypatch`](https://docs.pytest.org/en/latest/reference.html#_pytest.monkeypatch.MonkeyPatch)\n",
    "Built-in [`monkeypatch`](https://docs.pytest.org/en/latest/reference.html#_pytest.monkeypatch.MonkeyPatch) fixture lets you e.g. set environment variables and set/delete attributes of objects. The use cases are similar as with patching/mocking with `unittest.mock.patch`/`unittest.mock.MagicMock` which are part of the Python Standard Library.\n",
    "\n",
    "**Monkeypatching environment variables:**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "\n",
    "def get_env_var_or_none(var_name):\n",
    "    return os.environ.get(var_name, None)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%run_pytest[clean] '-s'\n",
    "\n",
    "def test_get_env_var_or_none_with_valid_env_var(monkeypatch):\n",
    "    monkeypatch.setenv('MY_ENV_VAR', 'secret')\n",
    "    res = get_env_var_or_none('MY_ENV_VAR')\n",
    "    assert res == 'secret'\n",
    "    \n",
    "def test_get_env_var_or_none_with_missing_env_var():\n",
    "    res = get_env_var_or_none('NOT_EXISTING')\n",
    "    assert res is None"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Monkeypatching attributes:**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class SomeClass:\n",
    "    some_value = 'some value'\n",
    "    \n",
    "    @staticmethod\n",
    "    def tell_the_truth():\n",
    "        print('This is the original truth')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def fake_truth():\n",
    "    print('This is modified truth')\n",
    "\n",
    "@pytest.fixture\n",
    "def fake_some_class(monkeypatch): \n",
    "    monkeypatch.setattr('__main__.SomeClass.some_value', 'fake value')\n",
    "    monkeypatch.setattr('__main__.SomeClass.tell_the_truth', fake_truth)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%run_pytest[clean] '-s'\n",
    "\n",
    "def test_some_class(fake_some_class):\n",
    "    print(SomeClass.some_value)\n",
    "    SomeClass.tell_the_truth()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### [`tmpdir`](https://docs.pytest.org/en/latest/tmpdir.html#the-tmpdir-fixture)\n",
    "[`tmpdir`](https://docs.pytest.org/en/latest/tmpdir.html#the-tmpdir-fixture) fixture provides functionality for creating temporary files and directories."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def word_count_of_txt_file(file_path):\n",
    "    with open(file_path, 'r') as f:\n",
    "        content = f.read()\n",
    "        return len(content.split())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%run_pytest[clean] '-s'\n",
    "\n",
    "def test_word_count(tmpdir):\n",
    "    test_file = tmpdir.join('test.txt')\n",
    "    test_file.write('This is example content of seven words')\n",
    "    res = word_count_of_txt_file(str(test_file)) # str returns the path\n",
    "    assert res == 7"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Fixture scope"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# scope is function also by default\n",
    "@pytest.fixture(scope='function')\n",
    "def func_fixture():\n",
    "    print('\\nfunc')\n",
    "    \n",
    "@pytest.fixture(scope='module')\n",
    "def module_fixture():\n",
    "    print('\\nmodule')\n",
    "    \n",
    "@pytest.fixture(scope='session')\n",
    "def session_fixture():\n",
    "    print('\\nsession')  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%run_pytest[clean] '-s'\n",
    "\n",
    "def test_something(func_fixture, module_fixture, session_fixture):\n",
    "    pass\n",
    "\n",
    "def test_something_else(func_fixture, module_fixture, session_fixture):\n",
    "    pass"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Setup-teardown behaviour"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "@pytest.fixture\n",
    "def some_fixture():\n",
    "    print('some_fixture is run now')\n",
    "    yield 'some magical value'\n",
    "    print('\\nthis will be run after test execution, you can do e.g. some clean up here')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%run_pytest[clean] '-s'\n",
    "\n",
    "def test_something(some_fixture):\n",
    "    print('running test_something')\n",
    "    assert some_fixture == 'some magical value'\n",
    "    print('test ends here')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Using fixtures automatically"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "@pytest.fixture(autouse=True)\n",
    "def my_fixture():\n",
    "    print('\\nusing my_fixture')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%run_pytest[clean] '-s'\n",
    "\n",
    "def test_1():\n",
    "    pass\n",
    "    \n",
    "def test_2():\n",
    "    pass"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
